package system

import (
	"context"
	"errors"
	"github.com/go-redis/redis/v8"
	"gorm.io/gorm"
	comRequest "server/model/common/request"
	sysRequest "server/model/system/request"
	"strconv"
	"time"

	//comRequest "server/model/common/request"
	"server/global"
	sysModel "server/model/system"
	//sysRequest "server/model/system/request"
)

type PermissionService struct {
}

var PermissionServiceApp = new(PermissionService)

// 获取所有权限列表
func (ps *PermissionService) GetAll() ([]*sysModel.SysPermission, error) {
	var list []*sysModel.SysPermission
	result := global.GvaDb.Order("order_num asc, id asc").Find(&list)
	return list, result.Error
}

// 获取用户所有权限列表
func (ps *PermissionService) GetUserPermStrList(userId string, obj string) ([]string, error) {
	var permStrs []string
	perms, err := ps.GetAll()
	if err != nil {
		return nil, err
	}
	for _, perm := range perms {
		if hasPerm, err := CasbinServiceApp.Casbin().Enforce(userId, obj, perm.Name); err != nil {
			return nil, err
		} else if hasPerm {
			permStrs = append(permStrs, perm.Name)
		}
	}
	return permStrs, nil
}

// BuildTree 构建树结构数据,(由于需要保留菜单结构数据，因此每一次树结构菜单都需要在对应的service中实现，使用公共的树方法无法还原原始菜单数据)
func (ps *PermissionService) BuildTree(resources []*sysModel.SysPermission) []*sysModel.SysPermission {
	//定义根树，既id=0的根节点，我用的时候并不存在于数据库
	var rootResource = sysModel.SysPermission{}
	//创建一个map，把父级相同的地址归纳起来
	DataMap := make(map[uint][]*sysModel.SysPermission, len(resources))
	//寻找对应的父级，添加子节点集合
	for key := range resources {
		pid := resources[key].ParentId
		DataMap[pid] = append(DataMap[pid], resources[key])
	}
	for key := range resources {
		if resources[key].ParentId == 0 {
			rootResource.Children = append(rootResource.Children, resources[key])
		}
		resources[key].Children = DataMap[resources[key].ID]
	}
	// 添加完成，既建立树形关系完成
	return rootResource.Children
}

//获取权限列表
func (ps *PermissionService) GetPermList(p *sysRequest.SysPermissionGetList) ([]*sysModel.SysPermission, error) {
	var list []*sysModel.SysPermission
	tx := global.GvaDb.Model(&sysModel.SysPermission{})
	if p.Name != "" {
		tx = tx.Where("name like ?", "%"+p.Name+"%")
	}
	if p.DisplayName != "" {
		tx = tx.Where("display_name like ?", "%"+p.DisplayName+"%")
	}
	rs := tx.Order("order_num ASC,id ASC").Find(&list)
	return list, rs.Error
}

// 保存权限数据
func (ps *PermissionService) SavePerm(d *sysModel.SysPermission) (int64, error) {
	var rs *gorm.DB
	if d.ID != 0 {
		rs = global.GvaDb.Where("id = ?", d.ID).Updates(d)
	} else {
		rs = global.GvaDb.Create(d)
	}
	//刷新面包屑数据
	ps.GetBreadcrumbList(d.ID, true)
	return rs.RowsAffected, rs.Error
}

// 获取面包屑列表数据
func (ps *PermissionService) GetBreadcrumbList(permId uint, isRefresh bool) ([]*sysModel.SysPermission, error) {
	//角色名称缓存KEY
	cacheKey := "sys_perm_breadcrumb_list:" + strconv.FormatUint(uint64(permId), 10)
	cacheStr, err := global.GvaRedis.Get(context.Background(), cacheKey).Result()
	var permList []*sysModel.SysPermission
	if err == redis.Nil || isRefresh == true {
		for permId != 0 {
			if permData, err := ps.GetPermById(permId); err == nil && permData.ID != 0 {
				permList = append([]*sysModel.SysPermission{permData}, permList...)
				permId = permData.ParentId
			} else {
				break
			}
		}
		//缓存角色列表
		permJsonStr, _ := json.Marshal(permList)
		if err := global.GvaRedis.Set(context.Background(), cacheKey, permJsonStr, 2*time.Hour).Err(); err != nil {
			return nil, err
		}
		return permList, nil
	}
	if err := json.Unmarshal([]byte(cacheStr), &permList); err != nil {
		return nil, err
	}
	return permList, nil
}

//通过权限名称获取权限数据
func (ps *PermissionService) GetPermByName(name string) (*sysModel.SysPermission, error) {
	var data *sysModel.SysPermission
	result := global.GvaDb.Where("name = ? ", name).Limit(1).Find(&data)
	return data, result.Error
}

// 通过权限ID获取权限数据
func (ps *PermissionService) GetPermById(permId uint) (*sysModel.SysPermission, error) {
	var data *sysModel.SysPermission
	result := global.GvaDb.Where("id = ? ", permId).Limit(1).Find(&data)
	return data, result.Error
}

// 删除权限
func (ps *PermissionService) DelPerm(ids *comRequest.IdsReq) (int64, error) {
	rs := global.GvaDb.Delete(&sysModel.SysPermission{}, ids.Ids)
	return rs.RowsAffected, rs.Error
}

// 获取指定父级ID的下级菜单列表
func (ps *PermissionService) GetPermChildList(ParentId uint) ([]*sysModel.SysPermission, error) {
	list := ([]*sysModel.SysPermission)(nil)
	rs := global.GvaDb.Model(&sysModel.SysPermission{}).Where("parent_id = ? ", ParentId).Order("order_num ASC,id ASC").Find(&list)
	return list, rs.Error
}

// 设置排序
func (ps *PermissionService) SetOrder(p *sysRequest.SysPermissionSetOrder) error {
	var err error
	switch p.DropType {
	case "before", "after": //拖拽到某个节点之前||拖拽到某个节点之后
		//使用事务闭包
		err = global.GvaDb.Transaction(func(tx *gorm.DB) error {
			var newOrderNum uint
			if p.DropType == "before" { //插到前面取被插队的排序数字作为排序，，后续累加一保持顺序
				newOrderNum = p.DropNode.OrderNum
			} else if p.DropType == "after" { //插到后面取插队排序数字加一作为排序，后续累加一保持顺序
				newOrderNum = p.DropNode.OrderNum + 1
			}
			//事先查出菜单列表
			sameLevelPermList, err := ps.GetPermChildList(p.DropNode.ParentId)
			if err != nil {
				return err
			}
			//拖拽节点更改排序数字及父级分类
			tx.Model(&sysModel.SysPermission{}).Where("id = ? ", p.DraggingNode.ID).Updates(sysModel.SysPermission{ParentId: p.DropNode.ParentId, OrderNum: newOrderNum})
			//调整之后节点的排序数字,之后的节点各加2，为插入节点留空隙
			isStartSet := false
			for _, perm := range sameLevelPermList {
				if perm.ID == p.DropNode.ID { //从放置节点开始后续排序自动加一
					isStartSet = true
					if p.DropType == "after" { //插入到后面，则被放置节点不处理
						continue
					}
					if p.DropType == "before" { //插入到前面，从被放置节点开始排序开始调整

					}
				}
				if isStartSet && perm.ID != p.DraggingNode.ID {
					newOrderNum++
					tx.Model(&sysModel.SysPermission{}).Where("id = ? ", perm.ID).Update("order_num", newOrderNum)
				}
			}
			//刷新面包屑数据
			ps.GetBreadcrumbList(p.DraggingNode.ID, true)
			return nil
		})
	case "inner": //拖进节点，直接更改父级ID
		err = global.GvaDb.Model(&sysModel.SysPermission{}).Where("id = ? ", p.DraggingNode.ID).Update("parent_id", p.DropNode.ID).Error
		//刷新面包屑数据
		ps.GetBreadcrumbList(p.DraggingNode.ID, true)
	default:
		err = errors.New("无效的节点类型")
	}
	return err
}
